//------------------------------------------------------------------------------
//SFWK Framework
//Copyright (C) 2011 SQLI

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Reflection;

using System.Linq.Expressions;
using System.Data.Linq;
using System.Data.Linq.Mapping;


using SFWK.Core;
using System.Diagnostics;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Data;
using SFWK.Core.DTO;
using SFWK.Core.ExpressionBuilder;
using System.Linq.Dynamic;
using SFWK.Core.Linq;

namespace SFWK.DAO
{
    /// <summary>
    /// Unic Access point to DAO objects
    /// Provides generic Submit, Attach, Detach, CRUD and Search methods
    /// Provides DataContext Access
    /// SubmitChanges method must be called to update data in DB!!!
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class SFWKEFRepository<TDataContext>
        where TDataContext : ObjectContext, new()
    {
        #region Accessors
        /// <summary>
        /// Accessor to data context
        /// </summary>
        public TDataContext DataContext
        {
            get
            {
                return DataContextFactory.GetScopedDataContext<TDataContext>();// EntityFrameworkHelper<TDataContext>.Context;
            }
        }
        #endregion

        #region Save
        /// <summary>
        /// Saves an entity (Insert or Update)
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="os"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        public TEntity Save<TEntity>(ObjectSet<TEntity> os, TEntity entity) where TEntity : class, IObjectWithChangeTracker, new()
        {
            TEntity workingEntity = SFWK.Core.EntityHelper.ValidateObject<TEntity>(entity); // For composition stuff, derived object can be passed -> transform derived to base object
            // LogHelper.Log(this, "SaveEntity", SFWK.Core.SerializeHelper.SerializeToXml(entity), ELogSeverity.Debug );
            // LogTracker("", entity);
            // LogObjectStateManager("Before apply Change", os.Context.ObjectStateManager);            
            os.ApplyChanges<TEntity>(workingEntity);
            // LogObjectStateManager("After apply Change", os.Context.ObjectStateManager);
            
            os.Context.SaveChanges();
            // LogObjectStateManager("After SAVE CHANGES", os.Context.ObjectStateManager);
            
            if (!entity.GetType().Equals(typeof(TEntity)))
                SFWK.Core.EntityHelper.CopyObjectValues<TEntity>(workingEntity, entity); // For composition stuff, derived object can be passed -> transform base to derived object
            //entity.AcceptChanges();

            // IMPORTANT we must accept change on all entities commited to the db before returning result.
            AcceptChangesOnAllEntities<TEntity>(os);
            return entity;
        }
        #endregion

        #region Delete
        /// <summary>
        /// Delete an Entity
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="os"></param>
        /// <param name="entity"></param>
        public void Delete<TEntity>(ObjectSet<TEntity> os, TEntity entity) where TEntity : class, IObjectWithChangeTracker, new()
        {
            entity = SFWK.Core.EntityHelper.ValidateObject<TEntity>(entity);// For composition stuff, derived object can be passed -> transform derived to base object
            os.ApplyChanges<TEntity>(entity);
            os.Context.DeleteObject(entity);
            os.Context.SaveChanges();
            entity.AcceptChanges();
            // AcceptChangesOnAllEntities<TEntity>(os);
        }
        #endregion

        private void LogObjectStateManager(String title, ObjectStateManager osm)
        {
            LogHelper.Log(osm, "DUMP STATE", title, ELogSeverity.Error);
            foreach(var x in osm.GetObjectStateEntries( EntityState.Added )) {
                if (x.Entity != null) LogHelper.Log(osm, "Added", SerializeHelper.SerializeXml( x.EntityKey)  , ELogSeverity.Error);
                else LogHelper.Log(osm, "Added", x.EntitySet + "-" + x.EntityKey , ELogSeverity.Error);
            }
            foreach(var x in osm.GetObjectStateEntries( EntityState.Deleted )) {
                if (x.Entity != null) LogHelper.Log(osm, "Deleted", SerializeHelper.SerializeXml(x.EntityKey), ELogSeverity.Error);
                else LogHelper.Log(osm, "Deleted", x.EntitySet + "-" + x.EntityKey, ELogSeverity.Error);
            }
            foreach(var x in osm.GetObjectStateEntries( EntityState.Modified )) {
                if (x.Entity != null) LogHelper.Log(osm, "Modified", SerializeHelper.SerializeXml(x.EntityKey), ELogSeverity.Error);
                else LogHelper.Log(osm, "Modified", x.EntitySet + "-" + x.EntityKey, ELogSeverity.Error);
            }
            foreach (var x in osm.GetObjectStateEntries(EntityState.Unchanged))
            {
                if (x.Entity != null) LogHelper.Log(osm, "Unchanged", SerializeHelper.SerializeXml(x.EntityKey), ELogSeverity.Error);
                else LogHelper.Log(osm, "Unchanged", x.EntitySet + "-" + x.EntityKey, ELogSeverity.Error);
            }
        }
        private void LogTracker(string tab, IObjectWithChangeTracker ioc)
        {
            LogHelper.Log(ioc, "Traker",tab+  ioc.ChangeTracker.State.ToString()+ ":" + ioc.ToString(), ELogSeverity.Error);
            foreach (var x in ioc.ChangeTracker.ObjectsAddedToCollectionProperties)
            {
                foreach( var y in x.Value) {
                    IObjectWithChangeTracker z = y as IObjectWithChangeTracker;
                    if (z != null) LogTracker(tab + " ADDED " + x.Key + "   ", z);
                }
                
            }
            foreach (var x in ioc.ChangeTracker.ObjectsRemovedFromCollectionProperties)
            {
                foreach (var y in x.Value)
                {
                    IObjectWithChangeTracker z = y as IObjectWithChangeTracker;
                    if (z != null) LogTracker(tab + " DELET " + x.Key + "   ", z);
                }

            }
            foreach (var x in ioc.ChangeTracker.OriginalValues)
            {
                LogHelper.Log(ioc, "Traker", tab + x.Key + "=" + x.Value.ToString(), ELogSeverity.Error);
            }

        }

      
        private static void AcceptChangesOnAllEntities<TEntity>(ObjectSet<TEntity> os) where TEntity : class, IObjectWithChangeTracker, new()
        {
            foreach (var entry in os.Context.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged))
            {
                IObjectWithChangeTracker ioc = entry.Entity as IObjectWithChangeTracker;
               if (ioc != null) ioc.AcceptChanges();
            }
        }

        #region Query builder methods
        /// <summary>
        /// Adds Where Clause to ObjectQuery from string filter
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="objectQuery"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        //protected IQueryable<T> setWhereClause<T>(IQueryable<T> queryable, string filter) where T : class
        //{
        //    return queryable.Where(!string.IsNullOrEmpty(filter) ? ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)) : x => true);
        //}

        /// <summary>
        /// Adds Order by Clause to ObjectQuery from string filter
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queryable"></param>
        /// <param name="orderBy"></param>
        /// <param name="defaultOrderBy"></param>
        /// <returns></returns>
        //protected IQueryable<T> setOrderByClause<T>(IQueryable<T> queryable, string orderBy, Expression<Func<T, object>> defaultOrderBy) where T : class
        //{
        //    return queryable.OrderBy(!string.IsNullOrEmpty(orderBy) ? ExpressionSerializerHelper<T>.DeserializeOrderBy(orderBy) : defaultOrderBy);
        //}

        /// <summary>
        /// Adds Include graph to query
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="objectQuery"></param>
        /// <param name="graphs"></param>
        /// <returns></returns>
        protected ObjectQuery<T> setInclude<T>(ObjectQuery<T> objectQuery, ControlledGraph<T> graph)
        {
            if (graph.Graph != null && graph.Graph.Count > 0)
                foreach (string val in graph.Graph)
                    if (val != null)
                        objectQuery = objectQuery.Include(val);
            return objectQuery;
        }

        /// <summary>
        /// Build query with include graph, where clause, and orderby clause
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="objectQuery"></param>
        /// <param name="graphs"></param>
        /// <param name="filter"></param>
        /// <param name="orderBy"></param>
        /// <param name="defaultOrderBy"></param>
        /// <returns></returns>
        protected IQueryable<T> BuildQuery<T>(ControlledGraph<T> graph, string filter, string orderBy)
            where T : class
        {
            IQueryable<T> res = setInclude<T>(this.DataContext.CreateObjectSet<T>(typeof(T).Name), graph).Where(!string.IsNullOrEmpty(filter) ? ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)) : x => true);
            if (!string.IsNullOrEmpty(orderBy))
            {
                Expression<Func<IQueryable<T>, IOrderedQueryable>> ex = ExpressionSerializerHelper<T>.DeserializeOrderBy(orderBy);
                if (ex != null)
                    res = ex.Compile().Invoke(res) as IQueryable<T>;
            }
            return res;
        }
        protected IQueryable<T> BuildQuery<T>(ControlledGraph<T> graph, string filter, Expression<Func<IQueryable<T>, IOrderedQueryable>> orderBy)
            where T : class
        {
            IQueryable<T> res = setInclude<T>(this.DataContext.CreateObjectSet<T>(typeof(T).Name), graph).Where(!string.IsNullOrEmpty(filter) ? ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)) : x => true);
            if (orderBy != null)
            {
                res = orderBy.Compile().Invoke(res) as IQueryable<T>;
            }
            return res;
        }

        

        protected IQueryable<T> BuildQuery<T>(string filter, string orderBy)
            where T : class
        {
            return BuildQuery<T>(null, filter, orderBy);
        }
        protected IQueryable<T> BuildQuery<T>(string filter, Expression<Func<IQueryable<T>, IOrderedQueryable>> orderBy)
            where T : class
        {
            return BuildQuery<T>(null, filter, orderBy);
        }
        
        #endregion

        #region SearchGraph
        /// <summary>
        /// Search for a list of Entities, and retrieve them with the requested linked object's graph list
        /// </summary>
        /// <typeparam name="T">Type of searched entities</typeparam>
        /// <param name="filter">serialized Where clause lambda expression</param>
        /// <param name="from">Paging: start index</param>
        /// <param name="range">Paging: number of record to get</param>
        /// <param name="orderBy">serialized Sort lambda expresion</param>
        /// <param name="graphs">List of linked object graphs to be retrieved</param>
        /// <param name="defaultOrderBy">If order by null, set a default order by</param>
        /// <returns>List of found records with the number of record available</returns>
        public SearchResultList<T> SearchGraph<T>(string filter, int? from, int? range, string orderBy, ControlledGraph<T> graph)
            where T : class
        {
            IQueryable<T> result = BuildQuery<T>(graph, filter, orderBy);
            int count = result.Count();
            if (from != null) result = result.Skip((int)from);
            if (range != null) result = result.Take((int)range);
            return new SearchResultList<T>(result.ToList(), count);
        }
        public SearchResultList<T> SearchGraph<T>(string filter, int? from, int? range, Expression<Func<IQueryable<T>, IOrderedQueryable>> orderBy, ControlledGraph<T> graph)
            where T : class
        {
            IQueryable<T> result = BuildQuery<T>(graph, filter, orderBy);
            int count = result.Count();
            if (from != null) result = result.Skip((int)from);
            if (range != null) result = result.Take((int)range);
            return new SearchResultList<T>(result.ToList(), count);
        }

        public SearchResultList<T> SearchGraph<T>(ControlledGraph<T> graph, Expression<Func<T, bool>> filter)
            where T : class
        {
            IQueryable<T> result = setInclude<T>(this.DataContext.CreateObjectSet<T>(typeof(T).Name), graph).Where(filter);
            int count = result.Count();
            return new SearchResultList<T>(result.ToList(), count);
        }
        #endregion SearchGraph

        #region Search
        /// <summary>
        /// Search for a list of Entities
        /// </summary>
        /// <typeparam name="T">Type of searched entities</typeparam>
        /// <param name="filter">serialized Where clause lambda expression</param>
        /// <param name="from">Paging: start index</param>
        /// <param name="range">Paging: number of record to get</param>
        /// <param name="orderBy">serialized Sort lambda expresion</param>
        /// <param name="defaultOrderBy">If order by null, set a default order by</param>
        /// <returns>List of found records with the number of record available</returns>
        private SearchResultList<T> _search<T>(string filter, int? from, int? range, object orderBy)
            where T : class
        {
            IQueryable<T> result = null;
            if (!string.IsNullOrEmpty(filter))
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name).Where(ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)));
            else
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name);

            if (orderBy != null)
            {
                if (orderBy is string && orderBy != string.Empty)
                {
                    Expression<Func<IQueryable<T>, IOrderedQueryable>> ex = ExpressionSerializerHelper<T>.DeserializeOrderBy((string)orderBy);
                    if (ex != null)
                        result = ex.Compile().Invoke(result) as IQueryable<T>;
                }
                else if (orderBy is Expression<Func<IQueryable<T>, IOrderedQueryable>>)
                {
                    result = ((Expression<Func<IQueryable<T>, IOrderedQueryable>>)orderBy).Compile().Invoke(result) as IQueryable<T>;
                }
            }

            // - 03.09.2010 - Only compute count if FROM value equals 0
            int? count = null;

            if (from.HasValue && from.Value == 0)
            {
                count = result.Count();
            }

            if (from != null) result = result.Skip((int)from);
            if (range != null) result = result.Take((int)range);
            var res = result.ToList();

            if (!from.HasValue && !range.HasValue)
            {
                count = res.Count;
            }

            return new SearchResultList<T>(res, count);
            //
        }

        public SearchResultList<T> Search<T>(string filter, int? from, int? range, string orderBy)
            where T : class
        { return _search<T>( filter,  from,  range, orderBy);}

        public SearchResultList<T> Search<T>(string filter, int? from, int? range, Expression<Func<IQueryable<T>, IOrderedQueryable>> orderBy)
            where T : class
        { return _search<T>(filter, from, range, orderBy); }




        public SearchResultList<T> Search<T>(string filter)
            where T : class
        {
            IQueryable<T> result = this.DataContext.CreateObjectSet<T>(typeof(T).Name).Where(ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)));
            // - 03.09.2010 - second database call for count has no utility
            var res = result.ToList();
            return new SearchResultList<T>(res, res.Count);
            //
        }
        public SearchResultList<T> Search<T>(Expression<Func<T, bool>> filter) where T : class
        {
            IQueryable<T> result = null;
            if (filter != null)
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name).Where(filter);
            else
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name);

            // - 03.09.2010 - second database call has no utility
            var res = result.ToList();
            return new SearchResultList<T>(res, res.Count);
            //
        }
        #endregion Search

        #region SearchIds
        public SearchResultList<TResult> SearchIds<T, TResult>(string filter, Expression<Func<T, TResult>> selectClause)
            where T : class
        {
            IQueryable<T> result = null;
            if (!string.IsNullOrEmpty(filter))
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name).Where(ExpressionSerializerHelper<T>.Deserialize(CompressionHelper.Decompress(filter)));
            else
                result = this.DataContext.CreateObjectSet<T>(typeof(T).Name);

            // - 03.09.2010 - Count from database has no utility --> directly count result list
            var res = result.Select(selectClause).ToList();
            return new SearchResultList<TResult>(res, res.Count);
            //
        }
        #endregion

        #region Get

        /// <summary>
        /// Gets an entity depending on the filter passed
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="filter"></param>
        /// <returns></returns>
        public T GetByClause<T>(Expression<Func<T, bool>> filter) where T : class
        {
            return this.DataContext.CreateObjectSet<T>(typeof(T).Name).Where(filter).FirstOrDefault();
        }

        public T GetGraphByClause<T>(Expression<Func<T, bool>> filter, ControlledGraph<T> graph) where T : class
        {
            return setInclude<T>(this.DataContext.CreateObjectSet<T>(typeof(T).Name), graph).Where(filter).FirstOrDefault();
        }
        #endregion


    }
}

